package middleware;

import java.sql.Connection;
import java.util.ArrayList;
import java.util.StringTokenizer;

import logging.Logging;

//implements the logic in case of full replication
public class ShardingDatabaseLogic extends AbstractDatabaseLogic {

	public ShardingDatabaseLogic(ArrayList<Connection> conns, Queue q) {
		super(conns, q);		
	}

	/* (non-Javadoc)
	 * @see middleware.AbstractDatabaseLogic#processQuery()
	 */
	@Override
	public void processStatement() {
		int type;
		StringBuilder resultString = new StringBuilder();

		// fetch query
		ClientHandler ch = this.fetchStatement();

		type = getType(ch);

		// send to database
		if (type == 0) {
			String queryString = ch.getQuery();
			Logging.logEvent(1, "PROCESSING UPDATE", queryString);

			// parse String
			// if delete: send to all databases
			if (queryString.trim().startsWith("RF2")) {
				// send query to all databases ...
				for (int i = 0; i < dbHandler.size(); i++) {				
					this.forwardUpdate(ch.getQuery(), i);	
				}
				// ... and fetch results afterwards
				for (int i = 0; i< dbHandler.size(); i++) {
					resultString.append(this.fetchResult(i));	
				}	


			} else {
				int index = 0;
				StringBuilder[] stringArray = new StringBuilder[dbHandler.size()];
				for (int i = 0; i < dbHandler.size(); i++) {
					stringArray[i] = new StringBuilder();
				}

				index = queryString.indexOf("RF1.1", index);
				index = queryString.indexOf("^", index);
				index++;
				int customerId = new Integer(queryString.substring(index, queryString.indexOf("^", index+1)));

				int dbNr = this.hashDB(customerId);

				index = queryString.indexOf("RF1.2", index);


				// send query to respective ...				
				this.forwardUpdate(queryString, dbNr);	
				// ... and fetch results afterwards
				resultString.append(this.fetchResult(dbNr));	
			}

			// modulo/hash calculation to assign to correct db

		} else if (type == 1){
			Logging.logEvent(2, "PROCESSING QUERY", ch.getQuery());
			// TODO: if time, only send to those dbs needed
			// send query to all databases ...
			for (int i = 0; i < dbHandler.size(); i++) {				
				this.forwardQuery(ch.getQuery(), i);	
			}

			// ... and fetch results afterwards

			// if query 1 then we have to merge the results
			if (ch.getQuery().startsWith("Q1")) {
				String[][] resultMatrix = null;
				String[][] currentMatrix = null;
				int rows = 4, columns = 9;

				String results;
				for (int i = 0; i< dbHandler.size(); i++) {
					results = this.fetchResult(i);	

					rows = this.getNrRows(results); 
					columns = this.getNrColumns(results);

					if (i == 0) {
						resultMatrix = this.parseResult(results);

					} else {
						currentMatrix = this.parseResult(results);

						for (int a = 0; a < rows; a++) {
							Float resultCount = new Float(resultMatrix[a][columns-1]);
							Float currentCount = new Float(currentMatrix[1][columns-1]);

							Float tmp;

							tmp = new Float(resultMatrix[a][2]) + new Float(currentMatrix[a][2]);
							resultMatrix[a][2] = tmp.toString();

							tmp = new Float(resultMatrix[a][3]) + new Float(currentMatrix[a][3]);
							resultMatrix[a][3] = tmp.toString();

							tmp = new Float(resultMatrix[a][4]) + new Float(currentMatrix[a][4]);
							resultMatrix[a][4] = tmp.toString();

							tmp = new Float(resultMatrix[a][5]) + new Float(currentMatrix[a][5]);
							resultMatrix[a][5] = tmp.toString();

							tmp = (new Float(resultMatrix[a][6]) * resultCount + new Float(currentMatrix[a][6]) * currentCount) / (resultCount + currentCount);
							resultMatrix[a][6] = tmp.toString();

							tmp = (new Float(resultMatrix[a][7]) * resultCount + new Float(currentMatrix[a][7]) * currentCount) / (resultCount + currentCount);
							resultMatrix[a][7] = tmp.toString();

							tmp = (new Float(resultMatrix[a][8]) * resultCount + new Float(currentMatrix[a][8]) * currentCount) / (resultCount + currentCount);
							resultMatrix[a][8] = tmp.toString();

							tmp = resultCount + currentCount;
							resultMatrix[a][9] = tmp.toString();							
						}

					}
				}	

				for (int a = 0; a < rows; a++) {
					for (int b = 0; b < columns; b++) {
						resultString.append(resultMatrix[a][b] + " \t ");
					}
					resultString.append("|");						
				}

			} else {

				for (int i = 0; i< dbHandler.size(); i++) {
					resultString.append(this.fetchResult(i));	
				}	
			}

		} else if (type == 2) {
			this.forwardPreparedStatementStruct(ch.getQuery());

			resultString.append("Set prepared Statement");
		}

		ch.returnResult(resultString.toString());

	}

	/**
	 * Parses the result and puts it in a two dimensional matrix
	 * @param str the result we want to have in matrix form
	 * @return the 2d matrix of strings
	 */
	private String[][] parseResult(String str) {

		int u, v;

		int rows = this.getNrRows(str);
		int columns = this.getNrColumns(str);

		String[][] res = new String[rows][columns];
		StringTokenizer st = new StringTokenizer(str, "|");
		u = 0;
		while (st.hasMoreTokens()) {

			StringTokenizer st2 = new StringTokenizer(st.nextToken(), " \t ");
			v = 0;
			while (st2.hasMoreTokens()) {
				res[u][v] = st2.nextToken().trim();
				v++;
			}
			u++;
		}

		return res;
	}

	/**
	 * Calculate the number of columns of a string representing the result (must be our format)
	 * @param str input string
	 * @return number of columns
	 */
	private int getNrColumns(String str) {

		int nrColumns = 0;		
		String currentStr = str.substring(0, str.indexOf("|") - 1);

		StringTokenizer st = new StringTokenizer(currentStr," \t ");
		while (st.hasMoreTokens()) {
			nrColumns++;
			st.nextToken();
		}

		return nrColumns;



	}

	/**
	 * Calculates the number of rows of a String representing the result (must be our format)
	 * @param str input string
	 * @return the number of rows
	 */
	private int getNrRows(String str) {

		int nrRows = 0;

		StringTokenizer st = new StringTokenizer(str,"|");
		while(st.hasMoreTokens()) {
			nrRows++;
			st.nextToken();
		}

		return nrRows;	
	}



	/**
	 * Implements logic of hash function used for sharding.
	 * @param cid customer id
	 * @return corresponding connection.
	 */
	private int hashDB(int cid) {
		// modulo #connections implementation so far
		// based on the realistic assumption of i.i.d. customer ids
		return cid % dbHandler.size();
	}


}
